/* eslint-disable @typescript-eslint/no-explicit-any */
import { JSXProps, h, JSXComponent } from '@knno/dom/jsx';
import { cellStyle, inlineStyle } from './cell.css';
import { DATA } from '../../components/symbol';
import { dom } from '@knno/dom';

export interface CellProps<T extends Cell<T, P, DataType>, P extends CellProps<T, P, DataType>, DataType>
	extends JSXProps<T> {
	// eslint-disable-next-line @typescript-eslint/no-explicit-any
	value?: DataType;
	textAlign?: 'left' | 'center' | 'right';
	inline?: boolean;
	colspan?: number;
	rowspan?: number;
	readonly?: boolean;
	optional?: boolean;
	title?: string;
}

export type Option = {
	code: unknown;
	name: string;
};

export type TreeOption = Option & {
	children?: Option[];
};

export type SetValueOption = {
	noEvents?: boolean;
	noUpdate?: boolean;
};

export abstract class Cell<
	T extends Cell<T, P, DataType>,
	P extends CellProps<T, P, DataType>,
	DataType
> extends JSXComponent<T> {
	protected [DATA]: DataType | null = null;
	constructor(protected attrs: P) {
		const td =
			attrs.inline === true ? (
				<span tabindex="0" class={inlineStyle} />
			) : (
				<td tabindex="0" class={cellStyle} colspan={attrs.colspan ?? 1} rowspan={attrs.rowspan ?? 1} />
			);
		if (attrs.textAlign) {
			td.css('textAlign', attrs.textAlign);
		}
		if (attrs.optional) {
			td.attr('optional', true);
		}
		if (attrs.title) {
			td.attr('title', attrs.title);
		}
		super(td, attrs);
		this[DATA] = attrs.value ?? null;
		this.init();
		const br = () => {
			if (this.elems()[0].childNodes.length === 0) {
				this.append(<br />);
				//this.append(dom.text(''));
			}
		};
		const observer = new MutationObserver(br);
		observer.observe(td.elems()[0], { childList: true, subtree: false });
		br();
	}
	emitChange(data: DataType | null) {
		this.emit(new CustomEvent('change', { bubbles: true, detail: data }));
	}

	getValue(): DataType | null {
		return this[DATA];
	}

	setValue(value: DataType | null, options?: SetValueOption) {
		const changed = this[DATA] !== value;
		this[DATA] = value;
		if (!options?.noUpdate) {
			this.update();
		}
		if (!options?.noEvents && changed) {
			this.emitChange(value);
		}
		return this;
	}

	protected abstract init(): void;
	protected abstract update(): void;
}
